package com.liuyjy.utils.utils;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * @Author liuyjy
 * @Date 2023/9/11 16:44
 * @Description: xss、sql检查
 **/
@Slf4j
public class XssAndSqlCleanRuleUtils {

    private final static Pattern[] scriptPatterns = {
            Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
            Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
    };

    private static String badStrReg = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";

    private static Pattern sqlPattern = Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);//整体都忽略大小写

    public static void main(String[] args) {
        String str = "第一阶段科学主题阅读+：学生登陆内蒙古中小学云图书馆服务平台（http://nm.web.syzton.com/），扫描本次活动专属二维码，注册成功后，进入中国科学发展史库，完成每天15分钟在线学习任务。平台将提供从自石器时代的科学启蒙至现代科技发展的详实知识，沿着历史的足迹学习，探寻解密中国科学发展的脉络与成就。\n" +
                "第二阶段在线闯关：完成在线学习任务的学生可登陆一体化平台，扫描二维码，进入为期15天的科学知识线上争霸活动，答题正确率超过85%的同学，将获得科学小达人荣誉称号。\n" +
                "第三阶段综合评比：10月26日答题入口关闭，进入综合评比环节。10月31日在云图平台公布答题结果，前100名优秀答题读者将获得科学阅读活动奖品及荣誉证书。同时根据参与活动的学生成绩，评选优秀学校和指导教师。";
        System.out.println(xssAndSqlCleanByStr(str));

    }

    /**
     * 字符串请求过滤
     *
     * @param value 值
     * @return
     */
    public static String xssAndSqlCleanByStr(String value) {
        if (EmptyUtil.isEmpty(value)) {
            return value;
        }
        //过滤xss字符集
        value = xssClean(value);
        //sql关键字检查
        try {
            return cleanSqlKeyWordsByStr(value);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return value;
    }

    /**
     * json字符串过滤
     *
     * @param value json字符串
     * @return String
     */
    public static String xssAndSqlCleanByJson(String value) {
        if (EmptyUtil.isEmpty(value)) {
            return value;
        }
        //过滤xss字符集
        value = xssClean(value);
        //sql关键字检查
        return cleanSqlKeyWordsByJson(value);

    }

    /**
     * 参数过滤
     *
     * @param value 值
     * @return
     */
    public static String xssAndSqlClean(String value) {

        if (EmptyUtil.isEmpty(value)) {
            return value;
        }

        //过滤xss字符集
        value = value.replaceAll("\0|\n|\r", "");
        for (Pattern pattern : scriptPatterns) {
            value = pattern.matcher(value).replaceAll("");
        }
        //value = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        return cleanSqlKeyWords(value);

    }

    /**
     * 参数过滤
     *
     * @param value 值
     * @return
     */
    public static String xssClean(String value) {

        if (EmptyUtil.isEmpty(value)) {
            return value;
        }

        //过滤xss字符集
        value = value.replaceAll("\0|\n|\r", "");
        for (Pattern pattern : scriptPatterns) {
            value = pattern.matcher(value).replaceAll("");
        }
        //value = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        return value;
    }

    /**
     * 解析参数SQL关键字
     *
     * @param value 值
     * @return String
     */
    private static String cleanSqlKeyWordsByStr(String value) throws UnsupportedEncodingException {

        //参数需要url编码
        //这里需要将参数转换为小写来处理
        //不改变原值
        //value示例 order=asc&pageNum=1&pageSize=100&parentId=0
        value = value.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
        value = value.replaceAll("\\+", "%2B");
        String lowerValue = URLDecoder.decode(value, "UTF-8").toLowerCase();

        //获取到请求中所有参数值-取每个key=value组合第一个等号后面的值
        boolean isContains = Stream.of(lowerValue.split("\\&"))
                .map(kp -> kp.substring(kp.indexOf("=") + 1))
                .parallel()
                .anyMatch(param -> {
                    if (sqlPattern.matcher(param).find()) {
                        log.error("参数中包含不允许sql的关键词:{}", param);
                        return true;
                    }
                    return false;
                });

        value = value.replaceAll("%25", "%");
        value = value.replaceAll("%2B", "\\+");
        return isContains ? "forbid" : value;
    }

    /**
     * 解析参数SQL关键字
     *
     * @param value 值
     * @return String
     */
    private static String cleanSqlKeyWords(String value) {

        String lowerValue = value.toLowerCase();

        if (sqlPattern.matcher(lowerValue).find()) {
            log.error("参数中包含不允许sql的关键词:{}", value);
            value = "forbid";
        }

        return value;
    }

    /**
     * 解析参数SQL关键字
     *
     * @param value 值
     * @return String
     */
    private static String cleanSqlKeyWordsByJson(String value) {

        JSONObject json = JSONObject.parseObject(value);
        Map<String, Object> map = json;
        Map<String, Object> mapjson = new HashMap<>();

        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String value1 = entry.getValue().toString();

            //这里需要将参数转换为小写来处理-不改变原值
            String lowerValue = value1.toLowerCase();

            if (sqlPattern.matcher(lowerValue).find()) {
                log.error("参数中包含不允许sql的关键词:{}", lowerValue);
                value1 = "forbid";
                mapjson.put(entry.getKey(), value1);
                break;
            } else {
                mapjson.put(entry.getKey(), entry.getValue());
            }

        }

        return JSONObject.toJSONString(mapjson);
    }
}